<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>ThreeLab · 星形与多边形着色器</title>

  <!-- 裸模块路径映射 -->
  <script type="importmap">
    {
      "imports": {
        "three": "./threejs/build/three.module.js",
        "three/addons/": "./threejs/examples/jsm/"
      }
    }
  </script>

  <style>
    html,body{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background:#000;}
    #box{width:100%;height:100%;}
    #info{
      position:fixed;
      top:8px;left:8px;
      color:#0ff;font-size:12px;
      pointer-events:none;
    }
  </style>
</head>

<body>
  <div id="box"></div>
  <div id="info">ThreeLab © 2024 防抄袭示例</div>

  <script type="module">
    /* ========================================================
       0. 模块导入
    ======================================================== */
    import * as THREE        from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { GUI }           from 'three/addons/libs/lil-gui.module.min.js';

    /* ========================================================
       1. 基础场景、相机、渲染器
    ======================================================== */
    const box      = document.getElementById('box');
    const scene    = new THREE.Scene();
    const camera   = new THREE.PerspectiveCamera(50, box.clientWidth/box.clientHeight, 0.1, 1000);
    camera.position.set(0, 0, 50);

    const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:true, logarithmicDepthBuffer:true });
    renderer.setSize(box.clientWidth, box.clientHeight);
    box.appendChild(renderer.domElement);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;

    /* ========================================================
       2. 着色器公共 Uniforms
    ======================================================== */
    const commonUniforms = {
      color    : { value: new THREE.Color(0xffffff * Math.random()) },
      uvScale  : { value: 0.1 },
      intensity: { value: 3 }
    };

    /* ========================================================
       3. 构建着色器材质
       - 顶点部分：仅传递 vUv
       - 片元部分：以 vUv 长度做径向渐变
    ======================================================== */
    const createMaterial = (color = commonUniforms.color.value) => new THREE.ShaderMaterial({
      uniforms: { ...commonUniforms, color:{ value:color } },
      vertexShader: `
        varying vec2 vUv;
        void main(){
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
        }
      `,
      fragmentShader: `
        varying vec2 vUv;
        uniform vec3  color;
        uniform float uvScale;
        uniform float intensity;

        void main(){
          vec2 uv = vUv * uvScale;
          float dist = length(uv);
          float alpha = smoothstep(0.0, 1.0, dist);   // 越中心越透明
          gl_FragColor = vec4(color * intensity, alpha);
        }
      `,
      transparent: true,
      side: THREE.DoubleSide
    });

    /* ========================================================
       4. 五角星
    ======================================================== */
    function createStarShape(rOuter, rInner, points){
      const shape = new THREE.Shape();
      const step  = Math.PI * 2 / points;
      for(let i=0;i<points;i++){
        const aO = i * step;
        const aI = aO + step * 0.5;
        const xO = Math.cos(aO) * rOuter;
        const yO = Math.sin(aO) * rOuter;
        const xI = Math.cos(aI) * rInner;
        const yI = Math.sin(aI) * rInner;
        i===0 ? shape.moveTo(xO,yO) : shape.lineTo(xO,yO);
        shape.lineTo(xI,yI);
      }
      shape.closePath();
      return new THREE.ShapeGeometry(shape);
    }

    const starGeo   = createStarShape(5, 2, 5);
    const starMat   = createMaterial();
    const starMesh  = new THREE.Mesh(starGeo, starMat);
    starMesh.position.y = 10;
    scene.add(starMesh);

    /* ========================================================
       5. 随机 4~8 边形
    ======================================================== */
    function createPolygonShape(radius, sides){
      const shape = new THREE.Shape();
      const step  = Math.PI * 2 / sides;
      for(let i=0;i<sides;i++){
        const ang = i * step;
        const x   = Math.cos(ang) * radius;
        const y   = Math.sin(ang) * radius;
        i===0 ? shape.moveTo(x,y) : shape.lineTo(x,y);
      }
      shape.closePath();
      return new THREE.ShapeGeometry(shape);
    }

    [4,5,6,7,8].forEach((sides, idx)=>{
      const geo  = createPolygonShape(5, sides);
      const mat  = createMaterial(new THREE.Color(Math.random()*0xffffff));
      const mesh = new THREE.Mesh(geo, mat);
      mesh.position.x = (idx - 2) * 10;
      scene.add(mesh);
    });

    /* ========================================================
       6. 天空盒（备用：纯色渐变）
       如需要立方体贴图，请把下面 createGradientSky() 换成
       new THREE.CubeTextureLoader().load(urls)
    ======================================================== */
    const createGradientSky = ()=>{
      const cvs = document.createElement('canvas');
      cvs.width = 512; cvs.height = 512;
      const ctx = cvs.getContext('2d');
      const g   = ctx.createLinearGradient(0,0,0,512);
      g.addColorStop(0,'#000');
      g.addColorStop(1,'#003');
      ctx.fillStyle = g; ctx.fillRect(0,0,512,512);
      return new THREE.CanvasTexture(cvs);
    };
    scene.background = createGradientSky();

    /* ========================================================
       7. GUI 调试面板
    ======================================================== */
    const gui = new GUI();
    gui.addColor(commonUniforms.color, 'value').name('颜色');
    gui.add(commonUniforms.intensity, 'value', 0, 10).name('亮度');
    gui.add(commonUniforms.uvScale,  'value', 0, 1).name('UV缩放');

    /* ========================================================
       8. 帧循环
    ======================================================== */
    function animate(){
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }
    animate();

    /* ========================================================
       9. 窗口自适应
    ======================================================== */
    window.addEventListener('resize', ()=>{
      camera.aspect = box.clientWidth / box.clientHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(box.clientWidth, box.clientHeight);
    });

    /* ========================================================
       10. ThreeLab 
    ======================================================== */
    console.log('%cThreeLab © 2024 - 星形着色器 Demo', 'color:#0ff;font-size:14px;background:#000;padding:4px 8px;border-radius:4px;');
  </script>
</body>
</html>